package org.naxx.security.parser;

import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.events.Attribute;
import javax.xml.stream.events.StartElement;
import javax.xml.stream.events.XMLEvent;

import org.naxx.security.algorithm.PolicyCombinigAlgorithm;
import org.naxx.security.model.Policy;
import org.naxx.security.model.PolicySet;
import org.naxx.security.model.Target;
import org.naxx.security.model.impl.PolicySetImpl;
import org.naxx.security.parser.util.QNames;
import org.naxx.security.util.Assert;

public class PolicySetParser extends AbstractXmlParser<PolicySet> {
	
	private TargetParser targetParser;
	private PolicyParser policyParser;
	
	public void setTargetParser(TargetParser targetParser) {
		this.targetParser = targetParser;
	}
	public void setPolicyParser(PolicyParser policyParser) {
		this.policyParser = policyParser;
	}

	/**
	 * Precondition: cursor points to element just before 
	 * opening PolicySet element.<br>
	 * Postcondition: cursor points to closing PolicySet element.
	 */
	public PolicySet parse(XMLEventReader reader) throws Exception {
		PolicySetImpl policySet = new PolicySetImpl();
		
		XMLEvent event = reader.nextTag(); // we now point to opening PolicySet element
		
		StartElement startElement = event.asStartElement();
		Attribute policySetIdAttr = startElement.getAttributeByName(QNames.POLICY_SET_ID_ATTRIBUTE);
		Attribute policyCombAlgorAttr = startElement.getAttributeByName(QNames.POLICY_COMBINING_ALGORITHM_ID_ATTRIBUTE);
		Attribute versionAttr = startElement.getAttributeByName(QNames.VERSION_ATTRIBUTE);
		
		policySet.setId(policySetIdAttr.getValue());
		policySet.setPolicyCombiningAlgorithm(PolicyCombinigAlgorithm.resolve(policyCombAlgorAttr.getValue()));
		if(versionAttr != null) {
			policySet.setVersion(versionAttr.getValue());
		}
		
		event = reader.nextTag();
		startElement = event.asStartElement();
		if("Description".equals(startElement.getName().getLocalPart())) {
			StringBuilder buffer = new StringBuilder();
			this.getCharacters(reader, buffer);
			policySet.setDescription(buffer.toString().trim());
			event = reader.nextTag(); // now at next opening element
		}
		startElement = event.asStartElement();
		// TODO not implemented
		if("PolicySetDefaults".equals(startElement.getName().getLocalPart())) {
			loop:
			while(true) {
				event = reader.nextEvent();
				if(event.getEventType() == XMLStreamConstants.END_ELEMENT && 
						"PolicySetDefaults".equals(event.asEndElement().getName().getLocalPart())) {
					break loop;
				}
			}
			event = reader.nextTag(); // we now at next start element
		}
		Target target = this.targetParser.parse(reader);
		policySet.setTarget(target); //we now at closing Target element
		
		if(reader.peek().getEventType() == XMLStreamConstants.END_ELEMENT) { // end of main policy set
			Assert.assertTrue("PolicySet".equals(reader.peek().asEndElement().getName().getLocalPart()), 
					"Expected end element of main PolicySet");
			reader.nextTag();
			return policySet;
		}
	
		while(true) {
			this.skipCharactersComments(reader);
			event = reader.peek();
			if(event.isEndElement()) {
				Assert.assertTrue(event.asEndElement().getName().getLocalPart().equals("PolicySet"), 
						"Expecting closing PolicySet element but found '" + event.asEndElement()
							.getName().getLocalPart() + "'");
				break;
			}
			startElement = event.asStartElement();
			String name = startElement.getName().getLocalPart();
			if("PolicySet".equals(name)) {
				PolicySet nestedPolicySet = this.parse(reader);
				policySet.addPolicySet(nestedPolicySet);
			}
			else if("Policy".equals(name)) {
				Policy policy = this.policyParser.parse(reader);
				policySet.addPolicy(policy);		
			}
			else if("PolicySetIdReference".equals(name)) {
				event = reader.nextTag(); // now at start element
				StringBuilder buffer = new StringBuilder();
				this.getCharacters(reader, buffer);
				policySet.addReferencedPolicySet(buffer.toString().trim());
			}
			else if("PolicyIdReference".equals(name)) {
				event = reader.nextTag(); // now at start element
				StringBuilder buffer = new StringBuilder();
				this.getCharacters(reader, buffer);
				policySet.addReferencedPolicy(buffer.toString().trim());
			}
			// TODO not implemented
			else if("CombinerParameters".equals(name)) {
				while(true) {
					event = reader.nextEvent();
					if(event.isEndElement() && "CombinerParameters".equals(event.asEndElement()
							.getName().getLocalPart())) {
						break;
					}
				}
			}
			// TODO not implemented
			else if("PolicyCombinerParameters".equals(name)) {
				while(true) {
					event = reader.nextEvent();
					if(event.isEndElement() && "PolicyCombinerParameters".equals(event
							.asEndElement().getName().getLocalPart())) {
						break;
					}
				}
			}
			// TODO not implemented
			else if("PolicySetCombinerParameters".equals(name)) {
				while(true) {
					event = reader.nextEvent();
					if(event.isEndElement() && "PolicySetCombinerParameters".equals(event
							.asEndElement().getName().getLocalPart())) {
						break;
					}
				}
			}
			else {
				throw new IllegalStateException("Unexpected elemenet '" + name + "'");
//				break;
			}
		}
		
		return policySet;
	}
}
